/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

package scenic.awt;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;

import scenic.CommandBuffer;
import scenic.SceneNode;
import scenic.ScenicColor;
import scenic.ScenicImage;

/**
 * Implementation of the Graphics2D class that uses the Scenic library
 * for rendering. This implementation does not use direct rendering,
 * instead it creates scene trees. After the scene tree is finished
 * it can be rendered on the screen or to an offscreen image.
 */
public class ScenicGraphics2D extends AbstractGraphics2D {
	private CommandBuffer cb = CommandBuffer.getInstance();

	private CommandBuffer.Context ctx;
			
	private static GraphicsConfiguration graphicsConfig = new ScenicGraphicsConfiguration();
	
	/**
	 * Constructs a new ScenicGraphics2D using the given scene as the root
	 * node.
	 * 
	 * @param scene the root node
	 */
	public ScenicGraphics2D(CommandBuffer.Context ctx) {
		this.ctx = cb.createContext(ctx);
	}

	/**
	 * Constructs a SceneGraphics2D that is a copy of the given
	 * SceneGraphics2D object. Note that the implementation of 
	 * ScenicGraphics2D is not totally compliant with the normal
	 * Graphics2D implementation. To avoid any problems, different
	 * SceneGraphics2D objects that share the same parent object should
	 * not be in parallel.
	 * 
	 * @param parent the parent object
	 */
	public ScenicGraphics2D(ScenicGraphics2D parent) {
		super(parent);
		transform = (AffineTransform)parent.transform.clone();
		ctx = cb.createContext(parent.ctx);
	}
	
	public void add(SceneNode node) {
	}
	
    public void setStroke(Stroke s) {
    	super.setStroke(s);
    	ctx.setStroke(s);
    }
    
    public void setColor(Color c) {
    	super.setColor(c);
    	if(c != null)
    		ctx.setPaint(c);
    }
    
    public void setPaint(Paint p) {
    	super.setPaint(p);
    	if(p != null)
    		ctx.setPaint(p);
    }
    
    public void setComposite(Composite c) {
    	super.setComposite(c);
   		ctx.setComposite(c);
    }
    
    public void setFont(Font f) {
    	super.setFont(f);
    	ctx.setFont(f);
    }
    
	@Override
	public AffineTransform getTransform() {
		return (AffineTransform)transform.clone();
	}


	@Override
	public void rotate(double theta, double x, double y) {
		transform.rotate(theta, x, y);
		ctx.concatenateTransform(AffineTransform.getRotateInstance(theta, x, y));
	}


	@Override
	public void rotate(double theta) {
		transform.rotate(theta);
		ctx.concatenateTransform(AffineTransform.getRotateInstance(theta));
	}


	@Override
	public void scale(double sx, double sy) {
		transform.scale(sx, sy);
		ctx.concatenateTransform(AffineTransform.getScaleInstance(sx, sy));
	}


	@Override
	public void setTransform(AffineTransform Tx) {
		ctx.setTransform(Tx);
		transform = (AffineTransform)Tx.clone();
	}


	@Override
	public void shear(double shx, double shy) {
		transform.shear(shx, shy);
		ctx.concatenateTransform(AffineTransform.getShearInstance(shx, shy));
	}


	@Override
	public void transform(AffineTransform Tx) {
		transform.concatenate(Tx);
		ctx.concatenateTransform(Tx);
	}


	@Override
	public void translate(double tx, double ty) {
		transform.translate(tx, ty);
		ctx.translate(tx, ty);
	}

	@Override
	public void translate(int tx, int ty) {
		transform.translate(tx, ty);
		ctx.translate(tx, ty);
	}

	private SceneNode paintShape(scenic.SceneShape shape) {
		Paint paint = getPaint();
		
		if(paint instanceof Color) {
			shape.setColor(new ScenicColor((Color)paint));
			return shape;
		}
		return shape;
	}
	
	private Shape clone(Shape s) {
		GeneralPath p = new GeneralPath();
		
		p.append(s.getPathIterator(null, 1.0 / 2.0 / transform.getDeterminant()), false);
		
		return p;
	}
	@Override
	public void draw(Shape s) {
		if(getStroke() instanceof BasicStroke) {
			ctx.draw(clone(s));
		} else {
			fill(getStroke().createStrokedShape(s));
		}
	}

	@Override
	public void fill(Shape s) {
		ctx.fill(clone(s));
	}

    public void fillRect(int x, int y, int width, int height){
    	ctx.fillRect(x, y, width, height);
    }

    public void drawRect(int x, int y, int width, int height){
		if(getStroke() instanceof BasicStroke) {
			ctx.drawRect(x, y, width, height);
		} else {
			super.drawRect(x, y, width, height);
		}
    }
    
	@Override
	public void clip(Shape s) {
		ctx.clip(s);
	}
	
	@Override
	public void setClip(Shape s) {
		ctx.setClip(s);
	} 
	
	@Override
	public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
	}

	@Override
	public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
	}

	@Override
	public void drawString(String str, float x, float y) {
		ctx.drawString(str, x, y);
	}

	@Override
	public void drawString(AttributedCharacterIterator iterator, float x,
			float y) {
		System.out.println("drawString using AttributedCharacterIterator not implemented!!!");
		// TODO Auto-generated method stub
	}

    /**
     * Renders the text of the specified
     * {@link GlyphVector} using
     * the <code>Graphics2D</code> context's rendering attributes.
     * The rendering attributes applied include the <code>Clip</code>,
     * <code>Transform</code>, <code>Paint</code>, and
     * <code>Composite</code> attributes.  The <code>GlyphVector</code>
     * specifies individual glyphs from a {@link Font}.
     * The <code>GlyphVector</code> can also contain the glyph positions.
     * This is the fastest way to render a set of characters to the
     * screen.
     *
     * @param g the <code>GlyphVector</code> to be rendered
     * @param x the x position in user space where the glyphs should be
     *        rendered
     * @param y the y position in user space where the glyphs should be
     *        rendered
     *
     * @see java.awt.Font#createGlyphVector(FontRenderContext, char[])
     * @see java.awt.font.GlyphVector
     * @see #setPaint
     * @see java.awt.Graphics#setColor
     * @see #setTransform
     * @see #setComposite
     * @see #setClip(Shape)
     */
    public void drawGlyphVector(GlyphVector g, float x, float y){
    	ctx.drawGlyphVector(g, x, y);
//    	ctx.fill(g.getOutline(x, y));
    }

    @Override
	public GraphicsConfiguration getDeviceConfiguration() {
    	System.out.println("getDeviceConfiguration !!!");
		return graphicsConfig;
	}

	@Override
	public java.awt.Graphics create() {
		return new ScenicGraphics2D(this);
	}

	@Override
	public void setXORMode(Color c1) {
		// TODO Auto-generated method stub

	}

	@Override
	public FontMetrics getFontMetrics(Font f) {
		// TODO Auto-generated method stub
		
		dummyGraphics.setTransform(transform);
		return dummyGraphics.getFontMetrics(f);
	}

	@Override
	public void copyArea(int x, int y, int width, int height, int dx, int dy) {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
		ScenicImage simg = convertToScenicImage(img);
	   	
		addObserver(img, observer);
		if(simg == null)
			return false;
		
		ctx.drawImage(simg, x, y);

		return true;
	}

	@Override
	public boolean drawImage(Image img, int x, int y, int width, int height,
			ImageObserver observer) {
		ScenicImage simg = convertToScenicImage(img);
	   	
		addObserver(img, observer);
		if(simg == null)
			return false;
		ctx.drawImage(simg, x, y, x + width, y + height, 
				0, 0, simg.getWidth(), simg.getHeight());

		return true; 
	}

	private void addObserver(Image img, ImageObserver observer) {
		if(observer != null) {
//			System.out.println("add observer");
			Toolkit.getDefaultToolkit().prepareImage(img, -1, -1, observer);
		}
	}
	
    /**
     * Draws as much of the specified area of the specified image as is
     * currently available, scaling it on the fly to fit inside the
     * specified area of the destination drawable surface. Transparent pixels
     * do not affect whatever pixels are already there.
     * <p>
     * This method returns immediately in all cases, even if the
     * image area to be drawn has not yet been scaled, dithered, and converted
     * for the current output device.
     * If the current output representation is not yet complete then
     * <code>drawImage</code> returns <code>false</code>. As more of
     * the image becomes available, the process that draws the image notifies
     * the specified image observer.
     * <p>
     * This method always uses the unscaled version of the image
     * to render the scaled rectangle and performs the required
     * scaling on the fly. It does not use a cached, scaled version
     * of the image for this operation. Scaling of the image from source
     * to destination is performed such that the first coordinate
     * of the source rectangle is mapped to the first coordinate of
     * the destination rectangle, and the second source coordinate is
     * mapped to the second destination coordinate. The subimage is
     * scaled and flipped as needed to preserve those mappings.
     * @param       img the specified image to be drawn
     * @param       dx1 the <i>x</i> coordinate of the first corner of the
     *                    destination rectangle.
     * @param       dy1 the <i>y</i> coordinate of the first corner of the
     *                    destination rectangle.
     * @param       dx2 the <i>x</i> coordinate of the second corner of the
     *                    destination rectangle.
     * @param       dy2 the <i>y</i> coordinate of the second corner of the
     *                    destination rectangle.
     * @param       sx1 the <i>x</i> coordinate of the first corner of the
     *                    source rectangle.
     * @param       sy1 the <i>y</i> coordinate of the first corner of the
     *                    source rectangle.
     * @param       sx2 the <i>x</i> coordinate of the second corner of the
     *                    source rectangle.
     * @param       sy2 the <i>y</i> coordinate of the second corner of the
     *                    source rectangle.
     * @param       observer object to be notified as more of the image is
     *                    scaled and converted.
     * @see         java.awt.Image
     * @see         java.awt.image.ImageObserver
     * @see         java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int)
     * @since       JDK1.1
     */
    public boolean drawImage(Image img,
                             int dx1, int dy1, int dx2, int dy2,
                             int sx1, int sy1, int sx2, int sy2,
                             ImageObserver observer) {
		ScenicImage simg = convertToScenicImage(img);
		
		addObserver(img, observer);
		if(simg == null)
			return false;
    	ctx.drawImage(simg, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2);
    	return true;
    }
    
    private ScenicImage convertToScenicImage(Image img) {
    	return ScenicImage.convert(img);
    }
    
	@Override
	public void dispose() {
		ctx.dispose();
	}

	@Override
	public Rectangle getClipBounds() {
		return new Rectangle(0, 0, 10000, 10000);
	}
	
	@Override
	public Shape getClip() {
		return null;
	}
	
}
